package sardine.server;

import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.http.*;
import sardine.*;
import sardine.Filter.SardineFilter;
import sardine.HttpMethod;
import sardine.Route.SardineRoute;
import sardine.exception.ExceptionHandler;
import sardine.exception.ExceptionMapper;
import sardine.log.Logs;
import sardine.route.RouteMatched;
import sardine.route.SimpleRouteMatcher;

import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;

import static io.netty.handler.codec.http.HttpHeaderUtil.isKeepAlive;

/**
 * @auth bruce_sha
 * @date 2015/5/31
 */
public class MatcherProcessor {

    // http重写
    static private final String HTTP_METHOD_OVERRIDE_HEADER = "X-HTTP-Method-Override";
    static private final String HTTP_METHOD_OVERRIDE_FORM = "_method";

    private SimpleRouteMatcher routeMatcher;
    private boolean hasStaticFilesHandlers;

    public MatcherProcessor(SimpleRouteMatcher routeMatcher, boolean hasStaticFilesHandlers) {
        this.routeMatcher = routeMatcher;
        this.hasStaticFilesHandlers = hasStaticFilesHandlers;
    }

    private static <T> T unException(Route<T> t, Request request, Response response) {
        try {
            return t.apply(request, response);
        } catch (Exception e) {
            throw new SardineException(e);
        }
    }

    private static void unException(Filter t, Request request, Response response) {
        try {
            t.apply(request, response);
        } catch (Exception e) {
            throw new SardineException(e);
        }
    }

    private void before() {

    }

    private void handle() {

    }

    private void after() {

    }

    private String path(FullHttpRequest request) {
        return new QueryStringDecoder(request.uri()).path();
    }

    /**
     * Header中的 X-HTTP-Method-Override 或 QueryString 中的 _method 拥有更高权重
     */
    private HttpMethod method(FullHttpRequest request) {

        final String method_override = request.headers().getAndConvert(HTTP_METHOD_OVERRIDE_HEADER);
        Optional<String> method = Optional.ofNullable(method_override);

        if (!method.isPresent()) {
            final Map<String, List<String>> query = new QueryStringDecoder(request.uri()).parameters();
            method = query.getOrDefault(HTTP_METHOD_OVERRIDE_FORM, Collections.emptyList()).stream().findFirst();
        }

        String httpMethod = method.orElse(request.method().toString()).toUpperCase();

        return HttpMethod.valueOf(httpMethod);
    }

    private String accept(FullHttpRequest request) {
        return request.headers().getAndConvert(HttpHeaderNames.ACCEPT);
    }

    public void process(final FullHttpRequest httpRequest,
                        final FullHttpResponse httpResponse,
                        final ChannelHandlerContext ctx) {

        HttpMethod method = method(httpRequest);
        String path = path(httpRequest);
        String accept = accept(httpRequest);

        Logs.debug(() -> "sardine method: " + method + ", uri: " + httpRequest.uri() + ", accept: " + accept);


        final RequestWrapper requestWrapper = new RequestWrapper();
        final ResponseWrapper responseWrapper = new ResponseWrapper();

        Response response = Response.SardineResponse.create(httpResponse);
        responseWrapper.delegate(response);

//        String bodyContent = null;

        try {
            // BEFORE filters
            List<RouteMatched> matches = routeMatcher.matches(HttpMethod.BEFORE, path, accept);

            matches.stream()
                    .filter(e -> e.target() instanceof SardineFilter)
                    .forEach(e -> {

                        if (requestWrapper.getDelegate() == null) {
                            Request request = Request.SardineRequest.create(e, httpRequest);
                            requestWrapper.setDelegate(request);
                        } else {
                            requestWrapper.changeMatch(e);
                        }

                        // 执行自定义的方法
                        unException((SardineFilter) e.target(), requestWrapper, responseWrapper);
                    });

//            for (RouteMatched filterMatch : matches) {
//                Object filterTarget = filterMatch.target();
//                if (filterTarget instanceof SimpleFilter) {
//                    SimpleFilter filter = (SimpleFilter) filterTarget;
//
//                    Request request = RequestResponseFactory.create(filterMatch, httpRequest);
//
//                    requestWrapper.delegate(request);
//                    responseWrapper.delegate(response);
//
//                    // 执行自定义的方法
//                    filter.apply(requestWrapper, responseWrapper);
//
//                    String bodyAfterFilter = response.body();
//                    //其实 bodyContent 的作用不大，应该封装在response中处理
//                    if (bodyAfterFilter != null) bodyContent = bodyAfterFilter;
//                }
//            }
            // BEFORE filters, END

            //before filter 中可能重写了uri
            path = path(httpRequest);
            Optional<RouteMatched> match = routeMatcher.match(method, path, accept);

            if (match.isPresent()) {

                RouteMatched routeMatched = match.get();
                Object target = routeMatched.target();

//            if (match != null) {
//                target = match.target();
//            } else if (method == HttpMethod.HEAD && bodyContent == null) {
//                // See if singleton is mapped to provide default HEAD mapping
//                bodyContent = routeMatcher.match(HttpMethod.GET, path, accept) != null ? "" : null;
//            }

//            if (target != null) {

//                try {
//                    String result = null;
                if (target instanceof SardineRoute) {
                    SardineRoute route = (SardineRoute) target;

                    if (requestWrapper.getDelegate() == null) {
                        Request request = Request.SardineRequest.create(routeMatched, httpRequest);
                        requestWrapper.setDelegate(request);
                    } else {
                        requestWrapper.changeMatch(routeMatched);
                    }

                    responseWrapper.delegate(response);

                    // TODO 条件
                    Object result = null;
                    if (route.condition(requestWrapper))
                        result = route.apply(requestWrapper, responseWrapper);
                    else result = null;

                    String resultAsString = route.render(result);
                    // result = element.toString(); // TODO: Remove later when render fixed
                    response.body(resultAsString);
                }
//                    if (result != null) {
//                        bodyContent = result;
//                        response.body(bodyContent);
//                    }
//                } catch (HaltException hEx) {
//                    throw hEx;
//                }
            }


            // AFTER filters
            matches = routeMatcher.matches(HttpMethod.AFTER, path, accept);

            matches.stream()
                    .filter(e -> e.target() instanceof SardineFilter)
                    .forEach(e -> {

                        if (requestWrapper.getDelegate() == null) {
                            Request request = Request.SardineRequest.create(e, httpRequest);
                            requestWrapper.setDelegate(request);
                        } else {
                            requestWrapper.changeMatch(e);
                        }

                        // 执行自定义的方法
                        unException((SardineFilter) e.target(), requestWrapper, responseWrapper);
                    });

//            for (RouteMatched filterMatch : matches) {
//                Object filterTarget = filterMatch.target();
//                if (filterTarget instanceof SimpleFilter) {
//
//                    if (requestWrapper.delegate() == null) {
//                        Request request = RequestResponseFactory.create(filterMatch, httpRequest);
//                        requestWrapper.delegate(request);
//                    } else {
//                        requestWrapper.changeMatch(filterMatch);
//                    }
//
//                    responseWrapper.delegate(response);
//
//                    SimpleFilter filter = (SimpleFilter) filterTarget;
//                    filter.apply(requestWrapper, responseWrapper);
//
//                    String bodyAfterFilter = response.body();//Access.getBody(response);
//
//                    if (bodyAfterFilter != null) bodyContent = bodyAfterFilter;
//                }
//            }
            // AFTER filters, END

        } catch (HaltException he) {

            Logs.debug(() -> "sardine halt: " + he);
//            if (response.status() == HttpResponseStatus.OK.code())
            response.status(he.statusCode());
            response.body(he.content());

        } catch (Exception e) {

            Logs.error("sardine error: ", e);

            Optional<ExceptionHandler> handler = ExceptionMapper.handler(e);
            if (handler.isPresent()) {
                Logs.debug(() -> "sardine exception handled.");

                handler.get().apply(e, requestWrapper, responseWrapper);
//                String bodyAfterFilter = response.body();
//                if (bodyAfterFilter != null) {
//                    bodyContent = bodyAfterFilter;
//                }
            } else {
                Logs.debug(() -> "sardine exception not handled.");

                response.status(HttpResponseStatus.INTERNAL_SERVER_ERROR);
                response.body(SardineBase.SERVER_ERROR);
            }
        }

//        bodyContent = response.body();

        boolean consumed = response.body() != null;

        // If redirected and content is null set to empty string to not throw NotConsumedException
        if (!consumed && responseWrapper.isRedirected()) {
            response.body("");
            consumed = true;
        }

        if (!consumed && hasStaticFilesHandlers) throw new NotConsumedException();

        if (!consumed) {
//            LOG.info("The requestPart route [" + uri + "] has not been mapped in sardine");
            response.status(HttpResponseStatus.NOT_FOUND);
            response.body(SardineBase.NOT_FOUND);
            consumed = true;
        }

        if (consumed) {
            if (!httpResponse.headers().contains(HttpHeaderNames.CONTENT_TYPE))
                httpResponse.headers().set(HttpHeaderNames.CONTENT_TYPE,
                        String.format("%s; charset=%s", SardineBase.DEFAULT_CONTENT_TYPE, StandardCharsets.UTF_8));

            httpResponse.content().writeBytes(response.body().getBytes(StandardCharsets.UTF_8));
            httpResponse.headers().setInt(HttpHeaderNames.CONTENT_LENGTH, httpResponse.content().readableBytes());
            if (isKeepAlive(httpRequest))
                httpResponse.headers().set(HttpHeaderNames.CONNECTION, HttpHeaderValues.KEEP_ALIVE);

            ctx.writeAndFlush(httpResponse);

            ChannelFuture lastContentFuture = ctx.writeAndFlush(LastHttpContent.EMPTY_LAST_CONTENT);
            if (!isKeepAlive(httpRequest)) lastContentFuture.addListener(ChannelFutureListener.CLOSE);
        }
    }
}